package com.zyz.system.service.exam.Impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zyz.common.core.constants.Constants;
import com.zyz.common.core.enums.ResultCodeEnum;
import com.zyz.common.core.model.TableDataInfo;
import com.zyz.common.security.exception.ServiceException;
import com.zyz.system.manager.ExamCacheManager;
import com.zyz.system.mapper.exam.ExamMapper;
import com.zyz.system.mapper.exam.ExamQuestionMapper;
import com.zyz.system.mapper.question.QuestionMapper;
import com.zyz.system.model.exam.Exam;
import com.zyz.system.model.exam.ExamQuestion;
import com.zyz.system.model.exam.dto.ExamAddDTO;
import com.zyz.system.model.exam.dto.ExamEditDTO;
import com.zyz.system.model.exam.dto.ExamQueryDTO;
import com.zyz.system.model.exam.dto.ExamQuestionAddDTO;
import com.zyz.system.model.exam.vo.ExamDetailVO;
import com.zyz.system.model.exam.vo.ExamVO;
import com.zyz.system.model.question.Question;
import com.zyz.system.model.question.vo.QuestionVO;
import com.zyz.system.service.exam.IExamService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper,ExamQuestion> implements IExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    @Override
    public TableDataInfo list(ExamQueryDTO examQueryDTO) {
//        log.info("examQueryDTO:{}", examQueryDTO);
        // 使用pagehelper来自动帮我们处理分页功能（自动帮忙修改我们的sql语句，在后面拼接limit x,x）
        PageHelper.startPage(examQueryDTO.getPageNumber(), examQueryDTO.getPageSize());
        List<ExamVO> examVOList = examMapper.selectExamList(examQueryDTO);
        if (examVOList == null || examVOList.isEmpty()) {
            return TableDataInfo.empty();
        }
        // 通过pageInfo拿到符合分页查询到的数据，并通过getTotal拿到个数
        long total = new PageInfo<>(examVOList).getTotal();
        return TableDataInfo.success(examVOList, total);
    }

    @Override
    public Long add(ExamAddDTO examAddDTO) {
        // 判断参数
        checkEditParams(examAddDTO,null);
        // 添加
        Exam exam = BeanUtil.copyProperties(examAddDTO, Exam.class);
        int row = examMapper.insert(exam);
        if (row != 1) {
            log.error("添加竞赛失败,竞赛title: {}", examAddDTO.getTitle());
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        // 添加成功 返回竞赛id
        return exam.getExamId();
    }


    @Override
    public void questionAdd(ExamQuestionAddDTO examQuestionAddDTO) {
        // 查询竞赛是否存在
        Exam exam = getExamById(examQuestionAddDTO.getExamId());
        if (Constants.PUBLISHED.equals(exam.getStatus())) {
            log.error("竞赛已经发布，禁止修改和删除，请先撤销发布后编辑");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_PUBLISHED);
        }
        // 判断竞赛是否已经开始
        checkStartExam(exam);
        // 查询题目是否存在
        Set<Long> questionIdSet = examQuestionAddDTO.getQuestionIdSet();
        if (CollectionUtil.isEmpty(questionIdSet)) {
            log.info("questionIdSet为空,questionIdSet:{}", questionIdSet);
            return;
        }
        List<Question> questions = questionMapper.selectBatchIds(questionIdSet);
        if (CollectionUtil.isEmpty(questions) || questions.size() < questionIdSet.size()) {
            log.error("未查询到要加入竞赛的题目,要加入的题目id:{} , 数据库现有题目: {}",questionIdSet, questions);
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_INSERT_QUESTION_NOT_EXISTS);
        }
        questionAddCore(questionIdSet, exam);
    }

    @Override
    public ExamDetailVO detail(Long examId) {

        // 创建返回值ExamDetailVO对象
        ExamDetailVO examDetailVO = new ExamDetailVO();
        // 判断竞赛是否存在并拿到竞赛对象
        Exam exam = getExamById(examId);
        // 将竞赛信息放入examDetailVO中
        BeanUtil.copyProperties(exam,examDetailVO);
        // 查询该竞赛下的竞赛题目
        LambdaQueryWrapper<ExamQuestion> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(ExamQuestion::getQuestionId)
                    .eq(ExamQuestion::getExamId, examId)
                    .orderByAsc(ExamQuestion::getQuestionOrder); // 根据order字段正序排列
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(queryWrapper);
        // 如果查出来为空，表示这个竞赛里面没有题目，是合法的，直接返回
        if (CollectionUtil.isEmpty(examQuestionList)) {
            return examDetailVO;
        }
        // 查询竞赛题目
        return QueryExamQuestions(examQuestionList, examDetailVO);
    }

    @Override
    public void edit(ExamEditDTO examEditDTO) {
        // 判断竞赛是否存在并拿到竞赛
        Exam exam = getExamById(examEditDTO.getExamId());
        if (Constants.PUBLISHED.equals(exam.getStatus())) {
            log.error("竞赛已经发布，禁止修改和删除，请先撤销发布后编辑");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_PUBLISHED);
        }
        // 判断竞赛是否已经开始
        checkStartExam(exam);
        // 参数判断（修改后的竞赛标题不能重复，所选择的竞赛时间等）
        checkEditParams(examEditDTO,examEditDTO.getExamId());
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        // 修改
        int row = examMapper.updateById(exam);
        if (row != 1) {
            log.error("竞赛修改失败,竞赛id: {}", exam.getExamId());
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
    }

    @Override
    public void questionDelete(Long examId, Long questionId) {
        // 删除前条件校验
        Exam exam = getExamById(examId);
        if (Constants.PUBLISHED.equals(exam.getStatus())) {
            log.error("竞赛已经发布，禁止修改和删除，请先撤销发布后编辑");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_PUBLISHED);
        }
        // 判断是否已经开赛
        checkStartExam(exam);
        // 根据竞赛id和题目id删除题目信息
        int row = examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
        if (row != 1) {
            log.error("竞赛题目删除失败,竞赛id: {}, 题目id: {}", examId, questionId);
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
    }

    @Transactional
    @Override
    public void delete(Long examId) {
        Exam exam = getExamById(examId);
        if (Constants.PUBLISHED.equals(exam.getStatus())) {
            log.error("竞赛已经发布，禁止修改和删除，请先撤销发布后编辑");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_PUBLISHED);
        }
        checkStartExam(exam);
        // 删除
        int rowExam = examMapper.deleteById(examId);
        if (rowExam != 1) {
            log.error("竞赛删除失败,竞赛id: {}, ", examId);
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        try {
            examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId, examId));
        } catch (Exception e) {
            log.error("竞赛删除失败,竞赛id: {}, ", examId);
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        // 删除成功
    }

    @Override
    public void publish(Long examId) {
        // 查询当前竞赛是否存在，并获取
        Exam exam = getExamById(examId);
        // 判断竞赛结束时间是否小于当前时间，如果是的  则禁止发布
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            log.error("比赛已结束，禁止发布");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_OVER);
        }
        // 查询竞赛中是否有题目，如果没有则不能发布
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        if (count == 0) {
            log.error("竞赛发布失败,竞赛中没有题目,竞赛id: {}, ", examId);
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_NOT_QUESTION);
        }
        // 修改竞赛状态为已发布
        exam.setStatus(Constants.PUBLISHED);
        // 将竞赛信息放入redis中
        examCacheManager.addCache(exam);
        int row = examMapper.updateById(exam);
        if (row != 1) {
            log.error("竞赛发布失败,竞赛id: {}, ", examId);
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        // 发布成功
    }

    @Override
    public void cancelPublish(Long examId) {
        // 查询当前竞赛是否存在，并获取
        Exam exam = getExamById(examId);
        // 判断竞赛结束时间是否小于当前时间，如果是的  则禁止撤销
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            log.error("比赛已结束，禁止撤销");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_NOT_PUBLISH);
        }
        // 查询当前是否已经开赛
        checkStartExam(exam);
        // 修改比赛状态
        exam.setStatus(Constants.UNPUBLISHED);
        // 删除redis中的竞赛信息
        examCacheManager.deleteCache(exam);
        int row = examMapper.updateById(exam);
        if (row != 1) {
            log.error("撤销发布竞赛失败,竞赛id: {}, ", examId);
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
    }

    private void checkStartExam (Exam exam) {
        // 如果开赛时间在当前时间之前  代表已经开赛
        if (exam.getStartTime().isBefore(LocalDateTime.now()) && exam.getEndTime().isAfter(LocalDateTime.now())) {
            log.error("当前竞赛已经开始，不得进行修改");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_STARTED);
        }
    }

    // ExamAddDTO 是 ExamEditDTO父类
    private void checkEditParams(ExamAddDTO editParams, Long examId) {
        // 判断竞赛题目是否重复
        List<Exam> examList = selectExamByTitle(editParams.getTitle(),examId);
        if (examList != null && !examList.isEmpty()) {
            log.error("竞赛标题不能重复，title: {}", editParams.getTitle());
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_TITLE_EXISTS);
        }
        // 判断竞赛时间不得早于目前时间开赛，结束时间不得早于开始时间
        if (editParams.getStartTime().isBefore(LocalDateTime.now())) {
            log.error("竞赛开始时间不得早于目前时间");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_START_TIME_BEFORE_CURRENT_TIME_NOT);
        }
        if (editParams.getStartTime().isAfter(editParams.getEndTime())) {
            log.error("竞赛开始时间不得晚于结束时间");
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_START_TIME_AFTER_END_TIME_NOT);
        }
    }

    /**
     *  查询竞赛题目
     * @param examQuestionList  从竞赛题目关联表中根据竞赛id查询出来的题目列表,返回的不是真正题目信息，而是关联表中的信息）
     * @param examDetailVO  查询完毕之后需要放入此返回对象字段<br/>LinkedHashSet<QuestionVO>返回
     * @return  封装好直接返回前端的 ExamDetailVO
     */
    private ExamDetailVO QueryExamQuestions(List<ExamQuestion> examQuestionList, ExamDetailVO examDetailVO) {
        // 表示查出来有题目信息，根据题目id将题目内容依次放入到examDetailVO的questionSet中
        List<Long> examQuestionIds = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();
        // 根据id集合查询题目数据
        List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>()
                .select(Question::getQuestionId, Question::getTitle, Question::getDifficulty)
                .in(Question::getQuestionId, examQuestionIds));
        LinkedHashSet<QuestionVO> questionVOs = new LinkedHashSet<>();
        // 类型转换
        for (Question question : questionList) {
            QuestionVO questionVO = BeanUtil.copyProperties(question, QuestionVO.class);
            questionVOs.add(questionVO);
        }
        examDetailVO.setQuestionSet(questionVOs);
        return examDetailVO;
    }

    private void questionAddCore(Set<Long> questionIdSet, Exam exam) {
        // 添加题目
        int order = 1;
        List<ExamQuestion> examQuestions = new ArrayList<>();
        for (Long questionId : questionIdSet) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setQuestionId(questionId);
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionOrder(order++);
            examQuestions.add(examQuestion);
        }
        // 批量添加
        boolean result = saveBatch(examQuestions);
        if (!result) {
            log.error("竞赛题目添加失败");
            throw new ServiceException(ResultCodeEnum.ERROR);
        }
        // 添加成功
    }

    private Exam getExamById(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            log.error("竞赛不存在，竞赛id: {}", examId);
            throw new ServiceException(ResultCodeEnum.FAILED_EXAM_NOT_EXISTS);
        }
        return exam;
    }

    /**
     * 根据竞赛标题查询竞赛
     * @param title 竞赛标题
     * @param excludeExamId 排除的竞赛id，如果传入为null，则不进行排除
     * @return 返回查询的结果
     */
    private List<Exam> selectExamByTitle(String title , Long excludeExamId) {
        LambdaQueryWrapper<Exam> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Exam::getTitle, title)
                .ne(excludeExamId != null,Exam::getExamId,excludeExamId);
        return examMapper.selectList(queryWrapper);
    }
}
